<?php

namespace App\Morefun\SerialNumber;

use App\Entities\SerialNumber;

final class Generator
{
    protected $channel;
    protected $letters = [
        0  => '0',
        1  => '1',
        2  => '2',
        3  => '3',
        4  => '4',
        5  => '5',
        6  => '6',
        7  => '7',
        8  => '8',
        9  => '9',
        10 => 'a',
        11 => 'b',
        12 => 'c',
        13 => 'd',
        14 => 'e',
        15 => 'f',
        16 => 'g',
        17 => 'h',
        18 => 'i',
        19 => 'j',
        20 => 'k',
        21 => 'l',
        22 => 'm',
        23 => 'n',
        24 => 'o',
        25 => 'p',
        26 => 'q',
        27 => 'r',
        28 => 's',
        29 => 't',
        30 => 'u',
        31 => 'v',
        32 => 'w',
        33 => 'x',
        34 => 'y',
        35 => 'z',
        36 => 'A',
        37 => 'B',
        38 => 'C',
        39 => 'D',
        40 => 'E',
        41 => 'F',
        42 => 'G',
        43 => 'H',
        44 => 'I',
        45 => 'J',
        46 => 'K',
        47 => 'L',
        48 => 'M',
        49 => 'N',
        50 => 'O',
        51 => 'P',
        52 => 'Q',
        53 => 'R',
        54 => 'S',
        55 => 'T',
        56 => 'U',
        57 => 'V',
        58 => 'W',
        59 => 'X',
        60 => 'Y',
        61 => 'Z',
        62 => '_',
        63 => '$',
    ];

    public static function channel($channel)
    {
        return new static($channel);
    }

    public function generate()
    {
        $current = SerialNumber::query()
            ->where('channel', $this->channel)
            ->lockForUpdate()
            ->first();

        if (!$current) {
            throw new \Exception("not found channel: {$this->channel}");
        }

        $current->increment('number');

        $number = $current->getAttribute('number');

        return $this->to64Hex($number) . $this->random();
    }

    protected function to64Hex($number)
    {
        $dividend = count($this->letters);

        $digits= [];
        while ($number > 0) {
            $r = $number % $dividend;
            $number = ($number - $r) / $dividend;
            $digits[] = $this->letters[$r];
        }

        return join('', array_reverse($digits));
    }

    protected function random()
    {
        return mt_rand(10000, 99999);
    }

    private function __construct($channel)
    {
        $this->channel = $channel;
    }

    private function __clone()
    {
    }
}
